﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

namespace MindFactorial.Library
{
    public enum Alignment: int
    {
        UI8 = 8,
        UI16 = 16,
        U32 = 32,
        U64 = 64
    }

    public class binary
    {
        StringBuilder internalbits = new StringBuilder();
        double number;

        #region [constructors]
        public binary(uint number)
        {
            this.number = number;
            SetBits(number, (int) Alignment.UI8);
        }

        public binary(uint number, Alignment alignment)
        {
            this.number = number;
            SetBits(number,(int)alignment);
        }

        public binary(string bits)
        {
            SetNumber(bits);
            SetBits((uint)number, bits.Length);
        }      

        #endregion

        #region [properties]

        public int NumberofBits
        {
            get
            {
                return internalbits.Length;
            }
        }
        public uint Number
        {
            get
            {
                return (uint)number;
            }
        }

        public bool this[int index]
        {
            get
            {
                if (internalbits[internalbits.Length - 1 - index] == '1')
                {
                    return true;
                }
                else
                    return false;
            }
            set
            {
                if (value == true)
                {
                    internalbits[internalbits.Length - 1 - index] = '1';

                }
                else
                    internalbits[internalbits.Length - 1 - index] = '0';

                SetNumber(internalbits.ToString());

            }
        }

        #endregion

        #region [Reading]
        public binary ReadLittleEndian(int startindex, int length)
        {
            string bits = internalbits.ToString();
            string temp = bits.Substring(startindex, length);
            return new binary(temp);
        }

        public binary ReadLittleEndian(int startindex)
        {
            string bits = internalbits.ToString();
            string temp = bits.Substring(startindex);
            return new binary(temp);
        }

        /// <summary>
        /// Reads the underlying bits from a particular length starting
        /// at a particular index
        /// </summary>
        /// <param name="index"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public binary Read(int startindex, int length)
        {
            Stack<char> bits = new Stack<char>();
            for (int i = startindex ; i < startindex + length; i++)
            {
                if (this[i])
                {
                    bits.Push('1');
                }
                else
                    bits.Push('0');
            } 
            string temp = new string(bits.ToArray());
            return new binary(temp );
        }

        /// <summary>
        /// Reads the underlying bits starting
        /// at a particular index till the end
        /// </summary>
        /// <param name="index"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public binary Read(int startindex)
        {
            Stack<char> bits = new Stack<char>();
            for (int i = startindex; i < internalbits.Length; i++)
            {
                if (this[i])
                {
                    bits.Push('1');
                }
                else
                    bits.Push('0');
            }
            string temp = new string(bits.ToArray());
            return new binary(temp);
        }

        #endregion

        public void Combine(binary binary)
        {
            internalbits.Append(binary.ToString());
            SetNumber(internalbits.ToString());
        }

        public override string ToString()
        {
            return internalbits.ToString();
        }

        #region [Private Methods]

        void SetNumber(string binary)
        {
            if (binary.Length > 32)
            {
                throw new Exception("Cannot exceed 32 bit integer limit");
            }
            number = 0;  //zero number out.  Each time this method is called a new number will be the outcome
            char[] bits = binary.ToCharArray();   //convert to array of chars representing each bit
            Array.Reverse(bits, 0, bits.Length);  //the array must be reversed so that the first number to the left becomes the highest factor

            for (int power = 0; power < bits.Length; power++)
            {
                if (bits[power] == '1' || bits[power] == '0')
                {
                    if (bits[power] == '1')
                    {
                        double total = Math.Pow(2d, (double)power);
                        number += total;
                    }
                }
                else
                    throw new FormatException("Invalid Number Format");
            }
        }

        void SetBits(uint number, int alignment)
        {
            
            bool started = false;

            //here i find the log of the number in base 2.
            //as you know the log is the anti square root, so this will give me the
            //square i need to apply to 2 in order to have a number closest to the given value.
            //this is an important step because it speeds up the processing by a mile.
            for (int power = (int) Math.Round(Math.Log(number, 2)); power > -1; power--)
            {
                double total = Math.Pow(2, power);
                if (total <= number)
                {
                    if (!started)
                        started = true;
                    internalbits.Append("1");
                    number = (uint)((double)number - total);
                }
                else
                {
                    if (started)
                    {
                        internalbits.Append("0");
                    }
                }
            }

            //increases the alignment of the number
            while (alignment < internalbits.Length)
                alignment += alignment; 

            int padleft = alignment - internalbits.Length;

            for (int i = 0; i < padleft; i++)
            {
                internalbits.Insert(0, '0');
            }

        }

        #endregion        

        #region [static]

        public static uint ConvertToDecimal(string b)
        {
            binary bin = new binary(b);
            return bin.Number;
        }

        public static string ConvertToBinary(uint number)
        {
            binary b = new binary(number);
            return b.ToString();
        }

        public static implicit operator binary(int number)
        {
            return new binary((uint)number);
        }

        public static implicit operator binary(string bits)
        {
            return new binary(bits);
        }

        public static implicit operator int(binary binary)
        {
            return (int)binary.Number;
        }

        #endregion

    }


}
